package com.gitee.qdbp.jdbc.plugins.impl;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.gitee.qdbp.able.jdbc.annotations.ColumnDefault;
import com.gitee.qdbp.able.jdbc.model.DbRawValue;
import com.gitee.qdbp.jdbc.model.SimpleFieldColumn;
import com.gitee.qdbp.jdbc.plugins.CommonFieldResolver;
import com.gitee.qdbp.jdbc.plugins.DbPluginHelper;
import com.gitee.qdbp.jdbc.plugins.JdbcDataTypeResolver;
import com.gitee.qdbp.jdbc.plugins.TableInfoScans;
import com.gitee.qdbp.jdbc.plugins.TableNameScans;
import com.gitee.qdbp.tools.parse.StringAccess;
import com.gitee.qdbp.tools.parse.StringParser;
import com.gitee.qdbp.tools.utils.ConvertTools;
import com.gitee.qdbp.tools.utils.StringTools;
import com.gitee.qdbp.tools.utils.VerifyTools;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * TableAnnotationScans的一种基础实现类
 *
 * @author zhaohuihua
 * @version 190601
 */
public abstract class BaseTableInfoScans implements TableInfoScans, DbPluginHelper.Aware {

    private final Logger log = LoggerFactory.getLogger(this.getClass());

    /** 插件容器 **/
    protected DbPluginHelper plugins;

    /** 扫描列信息 **/
    protected abstract SimpleFieldColumn scanColumn(Field field, Class<?> clazz);

    /**
     * 检查是不是需要排除的字段
     * 
     * @param field 字段信息
     * @param clazz 待扫描的类
     * @return 是否排除
     */
    protected boolean isIgnoreField(Field field, Class<?> clazz) {
        // 排除transient字段
        return Modifier.isTransient(field.getModifiers());
    }

    /** 扫描列信息之后的处理 **/
    protected void onAfterScanColumns(List<SimpleFieldColumn> allColumns) {
    }

    /** 表名扫描类 **/
    private TableNameScans tableNameScans;
    /** 判断是否公共字段的处理器 **/
    private CommonFieldResolver commonFieldResolver;

    /** 获取表名扫描类 **/
    public TableNameScans getTableNameScans() {
        return tableNameScans;
    }

    /** 设置表名扫描类 **/
    public void setTableNameScans(TableNameScans tableNameScans) {
        this.tableNameScans = tableNameScans;
    }

    /** 获取判断是否公共字段的处理器 **/
    public CommonFieldResolver getCommonFieldResolver() {
        return commonFieldResolver;
    }

    /** 设置判断是否公共字段的处理器 **/
    public void setCommonFieldResolver(CommonFieldResolver commonFieldResolver) {
        this.commonFieldResolver = commonFieldResolver;
    }

    @Override
    public String scanTableName(Class<?> clazz) {
        return getTableNameScans().scanTableName(clazz);
    }

    @Override
    public List<SimpleFieldColumn> scanColumnList(Class<?> clazz) {
        if (clazz == null) {
            throw new IllegalArgumentException("clazz is null");
        }

        // 字段顺序: 主键放在最前面, 然后是当前类自身的字段, 然后按继承顺序依次向上取父类的字段
        // 然后是公共包下的父类字段, 以及通过字段名指定的公共字段(创建人/创建时间/更新人/更新时间/逻辑删除标记)
        SimpleFieldColumn pkColumn = null;
        List<SimpleFieldColumn> commonColumns = new ArrayList<>();
        List<SimpleFieldColumn> allColumns = new ArrayList<>();
        Map<String, String> map = new HashMap<>();
        Class<?> temp = clazz;
        while (temp != null && temp != Object.class) {
            boolean isCommonPackage = isCommonPackage(temp.getPackage().getName());
            List<SimpleFieldColumn> innerCommonColumns = new ArrayList<>();
            List<SimpleFieldColumn> innerNormalColumns = new ArrayList<>();
            Field[] fields = temp.getDeclaredFields();
            for (Field field : fields) {
                // field.setAccessible(true); // 并没有读取字段值, 不需要修改Accessible
                String fieldName = field.getName();
                // 排除父类中已被子类覆盖的字段
                if (map.containsKey(fieldName)) {
                    continue;
                }
                map.put(fieldName, null);
                // 排除静态字段
                if (Modifier.isStatic(field.getModifiers())) {
                    continue;
                }
                // 其他排除条件
                if (isIgnoreField(field, temp)) {
                    continue;
                }

                // 扫描列信息
                SimpleFieldColumn column = scanColumn(field, temp);
                column.setJavaType(field.getType());
                if (column.getJdbcType() == null && Date.class.isAssignableFrom(field.getType())) {
                    column.setJdbcType(Types.TIMESTAMP);
                }
                // 判断是不是主键
                if (pkColumn == null && column.isPrimaryKey()) {
                    pkColumn = column;
                    // 主键默认设置Insertable/Updatable为true
                    pkColumn.setColumnInsertable(true);
                    pkColumn.setColumnUpdatable(true);
                    continue; // 当前列是ID, 单独记录下来, 插入到最前面
                }
                if (isCommonPackage || isCommonFieldName(fieldName)) {
                    innerCommonColumns.add(column);
                } else {
                    innerNormalColumns.add(column);
                }
            }
            if (!innerCommonColumns.isEmpty()) {
                commonColumns.addAll(0, innerCommonColumns); // 公共字段
            }
            if (!innerNormalColumns.isEmpty()) {
                allColumns.addAll(0, innerNormalColumns); // 普通字段
            }
            temp = temp.getSuperclass();
        }
        if (pkColumn != null) {
            allColumns.add(0, pkColumn); // ID插入到最前面
        }
        if (!commonColumns.isEmpty()) { // 公共字段插入到最后面
            allColumns.addAll(sortCommonColumns(commonColumns));
        }
        // 调用扫描后处理函数
        onAfterScanColumns(allColumns);
        return allColumns;
    }

    /** 扫描@ColumnDefault注解声明的默认值 **/
    protected void scanColumnDefault(Field field, SimpleFieldColumn column, Class<?> clazz) {
        ColumnDefault columnDefault = field.getAnnotation(ColumnDefault.class);
        if (columnDefault != null) {
            column.setColumnDefault(parseColumnDefault(columnDefault.value()));
        }
    }

    /** 解析默认值 **/
    // @ColumnDefault("1.00");
    // @ColumnDefault("CURRENT_TIMESTAMP");
    // @ColumnDefault("'N/A'"); // 字段串要用单引号括起来
    protected Object parseColumnDefault(String defaultValue) {
        if (VerifyTools.isBlank(defaultValue)) {
            return null;
        }
        defaultValue = defaultValue.trim();
        if (StringParser.isWrappedBySingleQuotationMark(defaultValue)) {
            return StringParser.unwrapSingleQuotationMark(defaultValue);
        } else if (StringParser.isWrappedByDoubleQuotationMark(defaultValue)) {
            return StringParser.unwrapDoubleQuotationMark(defaultValue);
        }
        if ("null".equalsIgnoreCase(defaultValue)) {
            return null;
        } else if ("true".equalsIgnoreCase(defaultValue) || "false".equalsIgnoreCase(defaultValue)) {
            // true/false在大部分的数据库都需要转换为1/0
            return new DbRawValue(defaultValue);
        } else {
            Number number = ConvertTools.toNumber(defaultValue, null);
            if (number != null) { // 是数字
                return number;
            } else { // 不是数字: CURRENT_TIMESTAMP
                return new DbRawValue(defaultValue);
            }
        }
    }

    /** 从列定义中解析列属性 **/
    // column_name type [NOT NULL|NULL] [DEFAULT default_value]
    //     [AUTO_INCREMENT] [UNIQUE [KEY]|[PRIMARY] KEY] [COMMENT 'string']
    // columnDefinition="Decimal(10,2) default 1.00"
    // columnDefinition="TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP"
    // columnDefinition="VARCHAR(20) DEFAULT 'N/A'" // 字段串要用单引号括起来
    // columnDefinition="INT(10) UNSIGNED NOT NULL AUTO_INCREMENT" // UNSIGNED AUTO_INCREMENT
    protected void parseColumnDefinition(SimpleFieldColumn column, String columnDefinition, Class<?> clazz) {
        JdbcDataTypeResolver resolver = plugins.getJdbcDataTypeResolver();
        // 从列定义中解析列属性
        StringAccess sa = new StringAccess(columnDefinition.trim());
        String dataType = sa.readWord(); // 读取一个单词
        if (dataType == null) {
            return; // 不是以单词开头, 无法识别
        }
        // 数据类型(如DECIMAL/INT/TIMESTAMP/VARCHAR/NVARCHAR/TEXT等)
        column.setDataType(dataType);
        // Decimal or TIMESTAMP or VARCHAR or INT
        String jdbcTypeString = resolver.getJdbcType(dataType.toUpperCase());
        Integer jdbcTypeNumber = resolver.parseDataType(jdbcTypeString);
        if (jdbcTypeNumber != null) {
            column.setJdbcType(jdbcTypeNumber);
        } else {
            String msg = "Can't resolve jdbcType '{}', from @Column(columnDefinition=\"{}\") of {}.{}, "
                    + "Please add mapping relationship to configuration file 'qdbc.datatype.txt'.";
            log.warn(msg, dataType, columnDefinition, clazz.getSimpleName(), column.getFieldName());
        }
        // 是否支持Unicode(如NVARCHAR/NCHAR/NTEXT等)
        if (resolver.supportUnicode(dataType)) {
            column.setSupportUnicode(true);
        }
        char typeNext = sa.skipWhitespace().peekChar();
        if (typeNext == 0) {
            return;
        }
        // (10,2) or null or (20) or (10)
        List<String> paramItems = sa.readMethodParams();
        if (paramItems != null && !paramItems.isEmpty()) {
            if (paramItems.size() == 1) {
                // VARCHAR(20)
                String length = paramItems.get(0);
                if (StringTools.isDigit(length)) {
                    if (resolver.supportDefinePrecision(jdbcTypeString)) {
                        column.setColumnPrecision(ConvertTools.toInteger(length));
                    } else if (resolver.supportDefineLength(jdbcTypeString)) {
                        column.setColumnLength(ConvertTools.toInteger(length));
                    }
                }
            } else { // > 1
                // DECIMAL(10,2)
                String precision = paramItems.get(0);
                String scale = paramItems.get(1);
                if (StringTools.isDigit(precision) && resolver.supportDefinePrecision(jdbcTypeString)) {
                    column.setColumnPrecision(ConvertTools.toInteger(precision));
                }
                if (StringTools.isDigit(scale) && resolver.supportDefineScale(jdbcTypeString)) {
                    column.setColumnScale(ConvertTools.toInteger(scale));
                }
            }
        }
        while (sa.isReadable() && sa.isLastReadSuccess()) {
            String nextWord = sa.skipWhitespace().readWord(); // 读取一个单词
            if ("NOT".equalsIgnoreCase(nextWord)) {
                String serialWord = sa.skipWhitespace().readWord(); // 再读取一个单词
                if ("NULL".equals(serialWord)) {
                    column.setColumnNullable(false);
                }
            } else if ("NULL".equalsIgnoreCase(nextWord)) {
                column.setColumnNullable(true);
            } else if ("DEFAULT".equalsIgnoreCase(nextWord)) {
                String defvalue = sa.skipWhitespace().readUniterm(' ', '\t', '\r', '\n');
                if (defvalue != null) {
                    column.setColumnDefault(parseColumnDefault(defvalue));
                }
            }
        }
    }

    private boolean isCommonPackage(String pkg) {
        return commonFieldResolver != null && commonFieldResolver.isCommonPackage(pkg);
    }

    private boolean isCommonFieldName(String fieldName) {
        return commonFieldResolver != null && commonFieldResolver.isCommonFieldName(fieldName);
    }

    private List<SimpleFieldColumn> sortCommonColumns(List<SimpleFieldColumn> columns) {
        if (commonFieldResolver == null) {
            return columns;
        } else {
            return commonFieldResolver.sortCommonFields(columns);
        }
    }

    /** 插件容器 **/
    @Override
    public void setPlugins(DbPluginHelper plugins) {
        this.plugins = plugins;
    }
}
